﻿Imports System.IO
Imports Microsoft.Data.Schema.Build
Imports Microsoft.Build.Evaluation
Imports Microsoft.Data.Schema.Extensibility
Imports Microsoft.Data.Schema.SchemaModel
Imports System.Xml
Imports Microsoft.Data.Schema
Imports Microsoft.Data.Schema.Sql
Imports Microsoft.Data.Schema.Sql.Build

Public Class DatabaseDeploy
    Private Const DBMODEL As String = "SourceModel"
    Private Const TARGETCONNECTION As String = "TargetConnectionString"
    Private Const TARGETDATABASE As String = "TargetDatabase"
    Private Const DEPLOYSCRIPT As String = "DeployScriptFileName"
    Private SQLCMDVARIBLEFILE As String = "SqlCommandVariablesFile"

    Private Shared _Initalized As Boolean = False
    Private Shared _errorCount As Integer = 0

    Friend Shared ReadOnly Property ErrorCount As Integer
        Get
            Return _errorCount
        End Get
    End Property

    Private Shared Sub Inialize()
        'Set the runtime version option so that you don't have to remember to in the app config.
        Dim rtInfo As ICLRRuntimeInfo = DirectCast(Runtime.InteropServices.RuntimeEnvironment.GetRuntimeInterfaceAsObject(Guid.Empty, GetType(ICLRRuntimeInfo).GUID), ICLRRuntimeInfo)
        rtInfo.BindAsLegacyV2Runtime()
        _Initalized = True
    End Sub

    Private Shared Function LoadExtensionManagerFromDBSchema(ByVal dbSchemaFile As FileInfo) As ExtensionManager
        Dim input As StreamReader = Nothing
        Dim manager2 As ExtensionManager
        Try
            input = New StreamReader(dbSchemaFile.FullName)
            Dim header As DataSchemaModelHeader = DataSchemaModel.ReadDataSchemaModelHeader(input, True)
            Dim manager As New ExtensionManager(header.DatabaseSchemaProviderName)
            If Not manager.DatabaseSchemaProvider.SchemaVersionSupported(header.SchemaVersion) Then
                'Throw New DeploymentFailedException(DataSchemaModel.GetUnsupportedDbSchemaVersionMessage(manager.DatabaseSchemaProvider.GetType.Name, header.SchemaVersion, dbSchemaFile.FullName))
                Throw New DeploymentFailedException()
            End If
            manager2 = manager
        Catch exception As XmlException
            Throw New DeploymentFailedException(exception.Message, exception)
        Catch exception2 As ModelSerializationException
            Throw New DeploymentFailedException(exception2.Message, exception2)
        Catch exception3 As ExtensibilityException
            Throw New DeploymentFailedException(exception3.Message, exception3)
        Finally
            If (Not input Is Nothing) Then
                input.Close()
                input = Nothing
            End If
        End Try
        Return manager2
    End Function

    Public Shared Sub DeployDatabase(ByVal ManifestfilePath As String,
                                      ByVal TargetConnectionString As String,
                                      ByVal TargetDB As String,
                                      Optional ByVal Properties As Dictionary(Of String, String) = Nothing)
        Dim sdController As ISchemaDeploymentController = Nothing
        Dim sdConstructor As SchemaDeploymentConstructor = Nothing
        Dim ProjectManifest As Project = Nothing
        If Not _Initalized Then
            Inialize()
        End If
        _errorCount = 0
        Try
            SendMessage("Loading database project files.", MessageTypeEnum.Message)

            Dim ManifestFile As New FileInfo(ManifestfilePath)
            ProjectManifest = New Project(ManifestfilePath)
            Dim EM As ExtensionManager = LoadExtensionManagerFromDBSchema(New FileInfo(Path.Combine(ManifestFile.DirectoryName, ProjectManifest.GetPropertyValue(DBMODEL))))

            Try
                ExtensionManager.AddExtensionLoadPath(My.Application.Info.DirectoryPath)
            Catch ex As InvalidOperationException
                'Do nothing
            Catch ex As Exception
                Throw ex
            End Try
            sdConstructor = EM.DatabaseSchemaProvider.GetServiceConstructor(
                Of SchemaDeploymentConstructor)()
            Dim ErrManager As New ErrorManager
            sdConstructor.Errors = ErrManager
            sdConstructor.Setup(New FileInfo(Path.Combine(ManifestFile.DirectoryName, ProjectManifest.GetPropertyValue(DBMODEL))),
                                TargetConnectionString)

            Dim SdEngine = sdConstructor.ConstructService
            ProjectManifest.SetProperty(TARGETCONNECTION, TargetConnectionString)
            ProjectManifest.SetProperty(TARGETDATABASE, TargetDB)

            SdEngine.Configure(ProjectManifest, ManifestFile.Directory)
            SdEngine.SetDeployToDatabase(False)
            SdEngine.SetDeployToScript(False, "")

            
            SendMessage("Updating command varibles.", MessageTypeEnum.Message)
            If Properties IsNot Nothing Then
                For Each Item As KeyValuePair(Of String, String) In Properties
                    If TypeOf SdEngine.Options Is Sql100SchemaDeploymentOptions Then
                        DirectCast(SdEngine.Options, Sql100SchemaDeploymentOptions).SourceSetVariables(Item.Key) = Item.Value
                    ElseIf TypeOf SdEngine.Options Is Sql90SchemaDeploymentOptions Then
                        DirectCast(SdEngine.Options, Sql90SchemaDeploymentOptions).SourceSetVariables(Item.Key) = Item.Value
                    Else
                        Throw New DeploymentFailedException("Unsupported Database schema model.")
                    End If
                Next
            End If
            
            SendMessage("Generating deployment plan.", MessageTypeEnum.Message)
            SdEngine.Execute()
            Dim DP = SdEngine.Plan

            SendMessage("Starting database deployment.", MessageTypeEnum.Message)
            sdController = SdEngine.CreateController
            AddHandler sdController.DeploymentMessage, AddressOf RecieveDeploymentMessage
            sdController.UpdateDatabase(DP)


            SendMessage("Finished deploying database project.", MessageTypeEnum.Message)
        Catch ex As Exception
            _errorCount += 1
            SendMessage("**** EXCEPTION OCCURED ****", MessageTypeEnum.Error)
            SendMessage(ex.Message, MessageTypeEnum.Error)
            SendMessage(ex.StackTrace, MessageTypeEnum.Error)
        Finally
            If sdController IsNot Nothing Then
                sdController.Dispose()
            End If
            If ProjectManifest IsNot Nothing Then
                ProjectCollection.GlobalProjectCollection.UnloadProject(ProjectManifest)
                ProjectManifest = Nothing
            End If
            GC.Collect()
            GC.WaitForPendingFinalizers()
        End Try
    End Sub

    Private Shared Sub RecieveDeploymentMessage(ByVal Sender As Object, ByVal DeployArgs As DeploymentContributorEventArgs)
        Dim iType As MessageTypeEnum = MessageTypeEnum.Unknown
        Select Case DeployArgs.Message.Severity
            Case ErrorSeverity.Error
                iType = MessageTypeEnum.Error
                _errorCount += 1
            Case ErrorSeverity.Message
                iType = MessageTypeEnum.Message
            Case ErrorSeverity.Warning
                iType = MessageTypeEnum.Warning
        End Select
        RaiseEvent DeploymentMessage(Sender,
                                     New DatabaseDeployEventArgs(DeployArgs.Message.Message,
                                                                 iType))
    End Sub

    Public Shared Event DeploymentMessage(ByVal sender As Object, ByVal e As DatabaseDeployEventArgs)

    Private Shared Sub SendMessage(ByVal Message As String, ByVal MessageType As MessageTypeEnum)
        RaiseEvent DeploymentMessage(Nothing, New DatabaseDeployEventArgs(Message, MessageType))
    End Sub

End Class
